package com.jackeysun.common.text

import android.content.Context
import android.graphics.Rect
import android.text.Spannable
import android.text.Spanned
import android.text.style.BackgroundColorSpan
import android.util.AttributeSet
import android.view.Gravity
import android.view.MotionEvent
import androidx.appcompat.widget.AppCompatTextView

/**
 *   Created by sunxunchao on 2021/12/31.
 */
open class SelectableTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr), IViewProtocol {

    companion object {
        const val DEFAULT_SHOW_DURATION = 100L
    }
    var selectionInfo: SelectionInfo = SelectionInfo()
    var floatWindow: IFloatWindow? = SimplePopupWindow(context)
    var isNormal: Boolean = false

    private var touchX: Float = 0f
    private var touchY: Float = 0f
    private var selectedText: CharSequence? = null
    private var selectRect: Rect = Rect()

    private var isHideWhenScroll = false
    private var isHide = true
    private val location = IntArray(2)
    private var touchRawY: Float = 0f
    private val initLocation = IntArray(2)
    private var visibleRect = Rect()

    override fun getSelectedText(): CharSequence? = selectedText
    override fun getSelectRect(): Rect = selectRect
    override fun getTouchX(): Float = touchX
    override fun getTouchY(): Float = touchY
    override fun getLocationInWindow(): IntArray = location



    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        getLocationOnScreen(initLocation)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        touchX = event.x
        touchY = event.y
        touchRawY = event.rawY
        if (isNormal) return super.onTouchEvent(event)
        if (event.action == MotionEvent.ACTION_MOVE ||
            event.action == MotionEvent.ACTION_DOWN
        ) {
            if (!isHide) {
                resetSelectionInfo()
                hideSelectView()
            }
        }
        if (event.action == MotionEvent.ACTION_UP) {
            if (isHide) {
                showSelectView(touchX, touchY)
            } else {
                resetSelectionInfo()
                hideSelectView()
            }
        }
        return super.onTouchEvent(event)
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        destroy()
    }

    override fun onPreDraw(): Boolean {
        if (isHideWhenScroll) {
            isHideWhenScroll = false
            postShowSelectView(DEFAULT_SHOW_DURATION)
        }
        return super.onPreDraw()
    }

    override fun onScrollChanged(horiz: Int, vert: Int, oldHoriz: Int, oldVert: Int) {
        super.onScrollChanged(horiz, vert, oldHoriz, oldVert)
        if (!isHideWhenScroll && !isHide) {
            isHideWhenScroll = true
            floatWindow?.dismiss()
        }
    }

    private fun showSelectView(x: Float, y: Float) {
        hideSelectView()
        resetSelectionInfo()
        isHide = false
        val startOffset = this.getTouchWordStart(x.toInt(), y.toInt())
        if (text is Spannable) {
            selectionInfo.mSpannable = this.text as Spannable
        }
        if (selectionInfo.mSpannable == null || startOffset >= this.text.length) {
            return
        }
        val line = layout.getLineForVertical(y.toInt())
        val lineStart = layout.getLineStart(line)
        val lineEnd = layout.getLineEnd(line)
        val lineContent = this.subChar(lineStart, lineEnd)
        var word: Word? = null
        if (lineContent != null) {
            word = matchWord(startOffset - lineStart, lineContent)
        }
        if (word == null) return
        selectText(word.start + lineStart, word.end + lineStart)
        show()
    }


    open fun show() {

        selectRect.left = layout.getPrimaryHorizontal(selectionInfo.mStart).toInt()
        selectRect.right = layout.getSecondaryHorizontal(selectionInfo.mEnd).toInt()
        selectRect.bottom = layout.getLineBottom(layout.getLineForOffset(selectionInfo.mStart))
        selectRect.top = layout.getLineTop(layout.getLineForOffset(selectionInfo.mStart))

        floatWindow?.setOnDismissListener {
            hideSelectView()
            resetSelectionInfo()
        }
        val w = floatWindow?.getWidth() ?: 0
        val h = floatWindow?.getHeight() ?: 0
        this.getLocationOnScreen(location)
        this.getLocalVisibleRect(visibleRect)


        if ((touchY - visibleRect.top) > h) {
            //up
            var posX: Int = selectRect.left + getLocationInWindow()[0]
            posX = (posX - (w - (selectRect.right - selectRect.left).toFloat()) / 2).toInt()
            var posY: Int = selectRect.top + getLocationInWindow()[1] - h - 16
            if (posX <= left) posX = left + 16
            if (posY < 0) posY = 16
            if (posX + w > right) {
                posX = right - 16 - w
            }
            floatWindow?.showAtLocation(this, Gravity.NO_GRAVITY, posX, posY)
        } else {
            //down
            var posX: Int = selectRect.left + getLocationInWindow()[0]
            posX = (posX - (w - (selectRect.right - selectRect.left).toFloat()) / 2).toInt()
            var posY: Int = selectRect.bottom + getLocationInWindow()[1] + 16
            if (posX <= left) posX = left + 16
            if (posY < 0) posY = 16
            if (posX + w > right) {
                posX = right - 16 - w
            }
            floatWindow?.showAtLocation(this, Gravity.NO_GRAVITY, posX, posY)
        }
    }

    private fun selectText(startPos: Int, endPos: Int) {
        selectionInfo.mStart = startPos
        selectionInfo.mEnd = endPos
        if (selectionInfo.mStart > selectionInfo.mEnd) {
            val temp: Int = selectionInfo.mStart
            selectionInfo.mStart = selectionInfo.mEnd
            selectionInfo.mEnd = temp
        }
        if (selectionInfo.mSpannable != null) {
            if (selectionInfo.mSpan == null) {
                selectionInfo.mSpan = BackgroundColorSpan(selectionInfo.backgroundColor)
            }
            this.selectedText =
                selectionInfo.mSpannable?.subSequence(selectionInfo.mStart, selectionInfo.mEnd)
                    .toString()
            selectionInfo.mSpannable?.setSpan(
                selectionInfo.mSpan,
                selectionInfo.mStart,
                selectionInfo.mEnd,
                Spanned.SPAN_INCLUSIVE_EXCLUSIVE
            )
        }
    }

    private fun hideSelectView() {
        isHide = true
        floatWindow?.dismiss()
    }

    private fun resetSelectionInfo() {
        this.selectedText = null
        selectionInfo.mSpannable?.removeSpan(selectionInfo.mSpan)
        selectionInfo.mSpan = null
    }

    private fun postShowSelectView(duration: Long) {
        this.removeCallbacks(mShowSelectViewRunnable)
        if (duration <= 0) {
            mShowSelectViewRunnable.run()
        } else {
            this.postDelayed(mShowSelectViewRunnable, duration)
        }
    }

    private val mShowSelectViewRunnable = Runnable {
        if (isHide) return@Runnable
        show()
    }

    private fun destroy() {
        resetSelectionInfo()
        hideSelectView()
    }

}